#include "Col.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Storage.h"

#include "String.h"
#include "Error.h"
#include "Object.h"
#include "ObjectTable.h"
#include "Math.h"

#include "ET_stdio.h"
#include "ET_stdlib.h"
#include "ET_errno.h"

#define MEM_DEBUG
#define MEM_STAT
#define MEM_CHECKOBJECTPOINTERS

#if defined(MEM_STAT) && !defined(MEM_DEBUG)
#       define MEM_DEBUG
#endif

#ifdef MEM_DEBUG
#   define MEM_MAGIC ((char)0xAB)
#   define storage_size(p) ((size_t)(((int*)p)[-2]))
#   define RealStart(p) ((char*)(p) - 2*sizeof(int))
#   define StoreSize(p, sz) (*((int*)(p))= (sz))
#   define ExtStart(p) ((char*)(p) + 2*sizeof(int))
#   define MemClear(p, start, len) \
		if ((len) > 0) memset(&((char*)(p))[(start)], 0, (len))
#   define RealSize(sz) ((sz) + 2*sizeof(int) + sizeof(char))
#   define StoreMagic(p, sz) *((char*)(p)+sz+2*sizeof(int))= MEM_MAGIC
#   define TestMagic(p, sz) (*((char*)(p)+sz) != MEM_MAGIC)
#   define CheckMagic(p, s, where) \
		if (TestMagic(p, s))    \
			Fatal(where, "storage area overwritten");
#   define CheckFreeSize(p, where) \
		if (storage_size((p)) < 0 || storage_size((p)) > Storage_maxblocksize) \
			Fatal(where, "unreasonable size (%d)", storage_size(p));
#   define RemoveStatMagic(p, where) \
		CheckFreeSize(p, where); \
		RemoveStat(p); \
		CheckMagic(p, storage_size(p), where)
#   define StoreSizeMagic(p, size, where) \
		StoreSize(p, size); \
		StoreMagic(p, size); \
		EnterStat(size, ExtStart(p)); \
		CheckObjPtr(ExtStart(p), where);
#else
#   define storage_size(p) ((size_t)0)
#   define RealSize(sz) (sz)
#   define RealStart(p) (p)
#   define ExtStart(p) (p)
#   define MemClear(p, start, len)
#   define StoreSizeMagic(p, size, where) \
		EnterStat(size, ExtStart(p)); \
		CheckObjPtr(ExtStart(p), where);
#   define RemoveStatMagic(p, where) \
		RemoveStat(p);
#endif


#define CallFreeHook(p, size) \
	if (Storage_freehook) Storage_freehook(Storage_freehookdata, (p), (size))

#ifdef MEM_CHECKOBJECTPOINTERS
#   define CheckObjPtr(p, name) ObjectTable::CheckPtrAndWarn((name), (p));
#else
#   define CheckObjPtr(p, name)
#endif

u_long Storage_lastobj, Storage_lastobjend;
bool Storage_proto;
static FreeHookFun Storage_freehook;
static void *Storage_freehookdata;
static size_t Storage_maxblocksize;
static const char *spaceErr= "storage exhausted";

void *Storage::_Calloc(int n, size_t sz)
{
	void *cp= ::calloc(n, sz);
	if (cp == 0)
		Fatal("Storage::_Calloc", spaceErr);
	return cp;
}

#ifdef MEM_STAT

	const int cObjMaxSize= 1024;

	static bool memStatistics;
	static int allocated[cObjMaxSize], freed[cObjMaxSize];
	static int allocatedTotal, freedTotal;
	static void **traceArray= 0;
	static int traceCapacity= 10, traceIndex= 0, memSize= -1, memIndex= -1;

	void EnterStat(size_t size, void *p)
	{
		if (memStatistics && size == memSize) {
			if (traceIndex == memIndex)
				Fatal("EnterStat", "trapped allocation %d", memIndex);
				
			if (traceIndex >= traceCapacity) {
				traceCapacity= traceCapacity*2;
				traceArray= (void**) realloc(traceArray, sizeof(void*)*traceCapacity);
			}
			traceArray[traceIndex++]= p;
		}
		Storage_maxblocksize= Math::Max(Storage_maxblocksize, size);
		if (size >= cObjMaxSize)
			allocated[cObjMaxSize-1]++;
		else
			allocated[size]++;
		allocatedTotal+= size;
	}

	void RemoveStat(void *vp)
	{
		size_t size= storage_size(vp);
		if (memStatistics && size == memSize) {
			for (int i= 0; i < traceIndex; i++)
				if (traceArray[i] == vp) {
					traceArray[i]= 0;
					break;
				}
		}
		if (size >= cObjMaxSize)
			freed[cObjMaxSize-1]++;
		else
			freed[size]++;
		freedTotal+= size;
	}

#else

#   define EnterStat(s, p) \
		Storage_maxblocksize= Math::Max(Storage_maxblocksize, s)
#   define RemoveStat(p)

#endif

//---- Storage -----------------------------------------------------------------

void *operator new(size_t size)
{
	static const char *where= "operator new";
	if (size < 0)
		Fatal(where, "size < 0");
	register void *vp= ::calloc(RealSize(size), sizeof(char));
	if (vp == 0)
		Fatal(where, spaceErr);
	StoreSizeMagic(vp, size, where);
	return ExtStart(vp);
}

void *operator new(size_t size, void *vp)
{
	static const char *where= "operator new(void *at)";
	if (size < 0)
		Fatal(where, "size < 0");
	if (vp == 0) {
		register void *vp= ::calloc(RealSize(size), sizeof(char));
		if (vp == 0)
			Fatal(where, spaceErr);
		StoreSizeMagic(vp, size, where);
		return ExtStart(vp);
	}
	return vp;
}

void operator delete(void *ptr)
{
	static const char *where= "operator delete";
	Storage_lastobj= Storage_lastobjend= 0;
	Storage_proto= FALSE;
	if (ptr) {
		CheckObjPtr(ptr, where);
		CallFreeHook(ptr, storage_size(ptr));
		RemoveStatMagic(ptr, where);
		MemClear(RealStart(ptr), 0, RealSize(storage_size(ptr)));
		::errno= 0;
		::free(RealStart(ptr));
		if (errno != 0)
			SysError(where, "free");
	}
}

void *Storage::ReAlloc(void *ovp, size_t size)
{
	static const char *where= "Storage::ReAlloc";
	if (size < 0)
		Fatal(where, "size < 0");
	if (ovp == 0)
		return new char[size];
	size_t oldsize= storage_size(ovp);
	if (oldsize == size)
		return ovp;
	RemoveStatMagic(ovp, where);
	void *vp= ::realloc((char*)RealStart(ovp), RealSize(size));
	if (vp == 0)
		Fatal(where, spaceErr);
	if (size > oldsize) {
		MemClear(ExtStart(vp), oldsize, size-oldsize);
	}
	StoreSizeMagic(vp, size, where);
	return ExtStart(vp);
}

void *Storage::ObjectAlloc(size_t sz)
{
	Storage_proto= FALSE;
	Storage_lastobj= (u_long) new char[sz];
	Storage_lastobjend= Storage_lastobj + sz;
	return (void*) Storage_lastobj;
}

void Storage::SetFreeHook(FreeHookFun fh, void *data)
{
	Storage_freehook= fh;
	Storage_freehookdata= data;
}

void Storage::Statistics()
{
#if defined(MEM_DEBUG) && defined(MEM_STAT)

	if (!memStatistics)
		return;
		
	fprintf(stderr, "\n");
	fprintf(stderr, "Mem statistics\n");
	fprintf(stderr, "%7s%7s%7s%7s\n", "size", "alloc", "free", "diff");
	fprintf(stderr, "============================\n");


	for (int i= 0; i < cObjMaxSize; i++)
		if (allocated[i] != freed[i])
		//if (allocated[i])
			fprintf(stderr, "%7d%7d%7d%7d\n", i, allocated[i], freed[i],
														allocated[i]-freed[i]);

	if (allocatedTotal != freedTotal) {
		fprintf(stderr, "----------------------------\n");
		fprintf(stderr, "total: %7d%7d%7d\n", allocatedTotal, freedTotal,
												allocatedTotal-freedTotal);
	}

	if (memSize != -1) {
		fprintf(stderr, "----------------------------\n");
		for (i= 0; i < traceIndex; i++)
			if (traceArray[i])
				fprintf(stderr, "block %d of size %d not freed\n", i, memSize);
	}
	fprintf(stderr, "============================\n");
	fprintf(stderr, "\n");
	fflush(stderr);
#endif
}

#ifdef MEM_STAT
	void Storage::EnableStatistics(int size, int ix)
	{
		memSize= size;
		memIndex= ix;
		memStatistics= TRUE;
	}
#else
	void Storage::EnableStatistics(int, int)
	{
	}
#endif

void Storage::FreeAll()
{
	Statistics();
}

